package aceim.protocol.snuk182.xmpp.common; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import org.jivesoftware.smack.PacketListener; import org.jivesoftware.smack.Roster; import org.jivesoftware.smack.RosterEntry; import org.jivesoftware.smack.RosterGroup; import org.jivesoftware.smack.RosterListener; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.filter.PacketFilter; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smackx.muc.MultiUserChat; import org.jivesoftware.smackx.muc.RoomInfo; import org.jivesoftware.smackx.packet.VCard; import aceim.api.dataentity.Buddy; import aceim.api.dataentity.BuddyGroup; import aceim.api.dataentity.ItemAction; import aceim.api.dataentity.OnlineInfo; import aceim.api.dataentity.PersonalInfo; import aceim.api.dataentity.ServiceMessage; import aceim.api.service.ApiConstants; import aceim.api.utils.Logger; import aceim.api.utils.Logger.LoggerLevel; public class XMPPRosterListener extends XMPPListener implements RosterListener, PacketListener, PacketFilter { private volatile boolean isContactListReady = false; //private final List<OnlineInfo> infos = Collections.synchronizedList(new ArrayList<OnlineInfo>()); private final Map<String, OnlineInfo> presenceCache = new ConcurrentHashMap<String, OnlineInfo>(); public XMPPRosterListener(XMPPServiceInternal service) { super(service); } @Override public void entriesUpdated(Collection<String> addresses) { clUpdated(); } @Override public void entriesDeleted(Collection<String> addresses) { clUpdated(); } @Override public void entriesAdded(Collection<String> addresses) { clUpdated(); } void clUpdated(){ List<BuddyGroup> groups = getContactList(); getInternalService().getService().getCoreService().buddyListUpdated( groups); } List<BuddyGroup> getContactList() { Roster roster = getInternalService().getConnection().getRoster(); Collection<RosterEntry> entries = roster.getEntries(); for (RosterEntry entry : entries) { if (entry.getName() == null) { try { VCard vc = new VCard(); vc.load(getInternalService().getConnection(), entry.getUser()); String nick = getNicknameFromVCard(vc); if (nick != null) { entry.setName(nick); } } catch (XMPPException e) { Logger.log(e); } } } List<BuddyGroup> groups = getInternalService().getService().getEntityAdapter().rosterGroupCollection2BuddyGroupList(roster.getGroups(), entries, getInternalService().getOnlineInfo().getProtocolUid(), getInternalService().getService().getContext(), getInternalService().getService().getServiceId()); return groups; } @Override public void presenceChanged(Presence presence) { Logger.log(" - presence " + presence.getFrom() + " " + presence.getMode(), LoggerLevel.VERBOSE); OnlineInfo info = getInternalService().getService().getEntityAdapter().presence2OnlineInfo(presence, getInternalService().getService().getContext(), getInternalService().getService().getServiceId(), getInternalService().getService().getProtocolUid(), presenceCache.get(getInternalService().getService().getEntityAdapter().normalizeJID(presence.getFrom()))); presenceCache.put(info.getProtocolUid(), info); if (isContactListReady) { getInternalService().getService().getCoreService().buddyStateChanged(Arrays.asList(info)); } } void checkCachedInfos() { while (presenceCache.size() > 0) { getInternalService().getService().getCoreService().buddyStateChanged(new ArrayList<OnlineInfo>(presenceCache.values())); presenceCache.clear(); } } @Override public boolean accept(Packet packet) { if (packet instanceof Presence) { Presence p = (Presence) packet; return ((Presence.Type.subscribe.equals(p.getType())) || (Presence.Type.unsubscribe.equals(p.getType())) || (Presence.Type.subscribed.equals(p.getType())) || (Presence.Type.unsubscribed.equals(p.getType()))); } return false; } @Override public void processPacket(Packet packet) { if (packet instanceof Presence) { Presence p = (Presence) packet; if (Presence.Type.subscribe.equals(p.getType())) { ServiceMessage message = new ServiceMessage(getInternalService().getService().getServiceId(), getInternalService().getService().getEntityAdapter().normalizeJID(p.getFrom()), true); message.setText(p.getFrom()); message.setContactDetail(getInternalService().getService().getContext().getString(R.string.ask_authorization)); getInternalService().getService().getCoreService().message(message); } else if (Presence.Type.unsubscribe.equals(p.getType())) { Presence ppacket = new Presence(Presence.Type.unsubscribed); ppacket.setTo(packet.getFrom()); ppacket.setFrom(packet.getTo()); getInternalService().getConnection().sendPacket(ppacket); } else if (Presence.Type.unsubscribed.equals(p.getType()) || Presence.Type.subscribed.equals(p.getType())) { clUpdated(); } else { presenceChanged(p); } } } void renameBuddy(Buddy buddy) { Roster roster = getInternalService().getConnection().getRoster(); RosterEntry buddyEntry = roster.getEntry(buddy.getProtocolUid()); buddyEntry.setName(buddy.getName()); getInternalService().getService().getCoreService().buddyAction(ItemAction.MODIFIED, buddy); } void renameGroup(final BuddyGroup buddyGroup) { new Thread() { @Override public void run() { try { RosterGroup rgroup = getInternalService().getService().getEntityAdapter().buddyGroup2RosterEntry(getInternalService().getConnection(), buddyGroup); for (RosterEntry entry : rgroup.getEntries()) { getInternalService().getConnection().getRoster().createEntry(entry.getUser(), entry.getName(), new String[] { buddyGroup.getName() }); } getInternalService().getService().getCoreService().groupAction(ItemAction.MODIFIED, buddyGroup); } catch (Exception e) { Logger.log(e); getInternalService().getService().getCoreService().notification( e.getLocalizedMessage()); } } }.start(); } void moveBuddy(final Buddy buddy) { new Thread() { @Override public void run() { try { Roster roster = getInternalService().getConnection().getRoster(); roster.createEntry(buddy.getProtocolUid(), buddy.getName(), (buddy.getGroupId() != null && buddy.getGroupId().equals(ApiConstants.NO_GROUP_ID)) ? new String[] { buddy.getGroupId() } : new String[0]); buddy.setGroupId(buddy.getGroupId()); getInternalService().getService().getCoreService().buddyAction(ItemAction.MODIFIED, buddy); } catch (XMPPException e) { Logger.log(e); getInternalService().getService().getCoreService().notification( e.getLocalizedMessage()); } } }.start(); } void removeGroup(final BuddyGroup buddyGroup) { new Thread() { @Override public void run() { try { RosterGroup rgroup = getInternalService().getService().getEntityAdapter().buddyGroup2RosterEntry(getInternalService().getConnection(), buddyGroup); Roster roster = getInternalService().getConnection().getRoster(); for (RosterEntry entry : rgroup.getEntries()) { //getInternalService().getConnection().getRoster().removeEntry(entry); roster.createEntry(entry.getUser(), entry.getName(), new String[0]); Buddy buddy = getInternalService().getService().getEntityAdapter().rosterEntry2Buddy(entry, buddyGroup.getOwnerUid(), getInternalService().getService().getContext(), buddyGroup.getServiceId()); buddy.setGroupId(ApiConstants.NO_GROUP_ID); getInternalService().getService().getCoreService().buddyAction(ItemAction.MODIFIED, buddy); } getInternalService().getService().getCoreService().groupAction(ItemAction.DELETED, buddyGroup); } catch (Exception e) { Logger.log(e); getInternalService().getService().getCoreService().notification( e.getLocalizedMessage()); } } }.start(); } void removeBuddy(final Buddy buddy) { new Thread() { @Override public void run() { try { getInternalService().getConnection().getRoster().removeEntry(getInternalService().getService().getEntityAdapter().buddy2RosterEntry(getInternalService().getConnection(), buddy)); getInternalService().getService().getCoreService().buddyAction(ItemAction.DELETED, buddy); } catch (XMPPException e) { Logger.log(e); getInternalService().getService().getCoreService().notification( e.getLocalizedMessage()); } } }.start(); } void addGroup(final BuddyGroup buddyGroup) { new Thread() { @Override public void run() { BuddyGroup g = new BuddyGroup(buddyGroup.getName(), buddyGroup.getOwnerUid(), buddyGroup.getServiceId()); g.setName(buddyGroup.getName()); getInternalService().getConnection().getRoster().createGroup(g.getName()); getInternalService().getService().getCoreService().groupAction(ItemAction.ADDED, g); } }.start(); } void addBuddy(final Buddy buddy) { Runnable r = new Runnable() { @Override public void run() { try { XMPPCommonService service = getInternalService().getService(); Roster roster = getInternalService().getConnection().getRoster(); roster.createEntry(buddy.getProtocolUid(), buddy.getName(), (buddy.getGroupId() != null && !buddy.getGroupId().equals(ApiConstants.NO_GROUP_ID)) ? new String[] { buddy.getGroupId() } : new String[0]); service.getCoreService().buddyAction( ItemAction.ADDED, service.getEntityAdapter().rosterEntry2Buddy( roster.getEntry(buddy.getProtocolUid()), service.getProtocolUid(), service.getContext(), service.getServiceId())); } catch (XMPPException e) { Logger.log(e); getInternalService().getService().getCoreService().notification( e.getLocalizedMessage()); } } }; Executors.defaultThreadFactory().newThread(r).start(); } /** * @return the isContactListReady */ public boolean isContactListReady() { return isContactListReady; } /** * @param isContactListReady the isContactListReady to set */ public void setContactListReady(boolean isContactListReady) { this.isContactListReady = isContactListReady; } public void loadCard(String jid) { Executors.defaultThreadFactory().newThread(new PersonalInfoRunnable(jid, PersonalInfoTarget.ICON, false)).start(); } public void getBuddyInfo(String jid, boolean shortInfo, boolean isMultiUserChat) { PersonalInfoTarget target; if (shortInfo) { target = PersonalInfoTarget.SHORT; } else { target = PersonalInfoTarget.ALL; } Executors.defaultThreadFactory().newThread(new PersonalInfoRunnable(jid, target, isMultiUserChat)).start(); } @Override void onDisconnect() { presenceCache.clear(); } private String getNicknameFromVCard(VCard card) { String fn; if (card.getNickName() != null && card.getNickName().length() > 0) { fn = card.getNickName(); } else { fn = card.getField("FN"); } return fn; } /** * @return the presenceCache */ public Map<String, OnlineInfo> getPresenceCache() { return presenceCache; } private class PersonalInfoRunnable implements Runnable { private final String uid; private final PersonalInfoTarget target; private final boolean isMultiUserChat; private PersonalInfoRunnable(String uid, PersonalInfoTarget target, boolean isMultiUserChat) { this.uid = uid; this.target = target; this.isMultiUserChat = isMultiUserChat; } @Override public void run() { PersonalInfo info = new PersonalInfo(getInternalService().getService().getServiceId()); info.setProtocolUid(uid); VCard card = new VCard(); if (isMultiUserChat) { try { RoomInfo room = MultiUserChat.getRoomInfo(getInternalService().getConnection(), uid); info.getProperties().putCharSequence(PersonalInfo.INFO_CHAT_DESCRIPTION, room.getDescription()); info.getProperties().putCharSequence(PersonalInfo.INFO_CHAT_OCCUPANTS, room.getOccupantsCount() + ""); info.getProperties().putCharSequence(PersonalInfo.INFO_CHAT_SUBJECT, room.getSubject()); getInternalService().getService().getCoreService().personalInfo(info, false); return; } catch (XMPPException e) { Logger.log(e.getLocalizedMessage(), LoggerLevel.DEBUG); } } try { card.load(getInternalService().getConnection(), uid); switch (target) { case ALL: for (String prop: getAllFieldsOfCard(card).keySet()){ info.getProperties().putCharSequence(prop, card.getField(prop)); } case SHORT: String fn = getNicknameFromVCard(card); if (fn != null) { info.getProperties().putString(PersonalInfo.INFO_NICK, fn); } getInternalService().getService().getCoreService().personalInfo(info, true); case ICON: if (card.getAvatar() != null) { getInternalService().getService().getCoreService().iconBitmap( uid, card.getAvatar(), card.getAvatarHash()); } break; } } catch (XMPPException e) { Logger.log(e); } } @SuppressWarnings("unchecked") private final Map<String, String> getAllFieldsOfCard(VCard card) { try { Field f = VCard.class.getDeclaredField("otherSimpleFields"); f.setAccessible(true); return (Map<String, String>) f.get(card); } catch (Exception e) { Logger.log(e); } return Collections.emptyMap(); } } private enum PersonalInfoTarget { ALL, SHORT, ICON } public void authorizationResponse(String contactUid, boolean accept) { Presence subscribe = new Presence(accept ? Presence.Type.subscribed : Presence.Type.unsubscribed); subscribe.setTo(contactUid); getInternalService().getConnection().sendPacket(subscribe); } public void uploadIcon(final byte[] bytes) { Executors.defaultThreadFactory().newThread(new Runnable() { @Override public void run() { try { VCard card = new VCard(); card.load(getInternalService().getConnection(),getInternalService().getService().getProtocolUid()); card.setAvatar(bytes); card.save(getInternalService().getConnection()); getInternalService().getService().getCoreService().iconBitmap(getInternalService().getService().getProtocolUid(), bytes, card.getAvatarHash()); } catch (XMPPException e) { Logger.log(e); } } }).start(); } }